<?php
// @todo: SORT HEADER SHOWOFF
class Apps_Plugin_Grid {
    const EXPORT_KEYWORD_TABLE = '{TABLE}';

    const CONTROL_AJAX    = 'ajax';
    const CONTROL_CLASSIC = 'classic';

    const COLUMNS_IN_SELECT = 0;

    // Config
        protected $_rowPerPageOptions = array(
                                            1  	 => 1,
        									5	 => 5,
                                            10 	 => 10,
                                            20 	 => 20,
                                            50 	 => 50,
                                            100  => 100,
                                            200  => 200,
                                            300  => 300,
                                            500  => 500,
                                            1000 => 1000,
                                            null => 'Không phân trang'
                                        );

        protected $_gridNamePrefix  = 'g';
        protected $_gridControlList = array(self::CONTROL_AJAX, self::CONTROL_CLASSIC);
        protected $_gridControl     = self::CONTROL_AJAX;

        protected $_elementClass = array(
           'checkbox'    => '',
           'checkboxAll' => 'check-all'
        );
        protected $_elementName = array(
           'checkbox'    => 'item_checker[]'
        );
        protected $_checkboxDataField = 'id';

        protected $_sortOptions = array(
           'sortIndexFieldBefore'      => 'sb',
           'sortIndexFieldCurrent'     => 'sc'
        );

        protected $_paginationOptions = array(
           'currentPageNumber'         => 'pp',
           'itemCountPerPage'          => 'pr',
           'defaultCurrentPageNumber'  => 1,
           'defaultItemCountPerPage'   => 100,

           'scrollingStyle'            => 'elastic', //'Sliding',
           'partial'                   => 'Grid/Pagination/control.phtml',
           'params'                    => null
        );
        protected $_viewScriptPath = '';

    // Plugin
        protected $_ajaxHandler;
        protected $_enableAutoResolveXmlHttpRequest;

        protected $_enableExport;

    // Resources
        protected $_gridName;
        protected $_resourceData;

        protected $_resourcePaginator;
        protected $_resourceTrAttributes = array();
        protected $_resourceTableCaption = null;
        protected $_resourceTableWidth = null;

        protected $_resourceSortableFieldMap;
        protected $_resourceColumnHiddenMask;
        protected $_resourceAllowSort = true;

        protected $_resourceSearch;
        // Format
            protected $_formatTableColumn = array();
            protected $_formatTableHeader;
            protected $_formatTableData;

            protected $_formatExcelTableHeader;
            protected $_formatExcelTableData;

        protected $_sortSortby;
        protected $_sortSorthow;

    // Parsed data
        protected $_xhtmlTableHeader;
        protected $_xhtmlTableData;
        protected $_xhtmlTableFooter;
        protected $_xhtmlTableExport;
        protected $_xhtmlPaginationControl;

        protected $_excelTableHeader;
        protected $_excelTableData;

        protected $_jsonTableData;

    // Resource-Setup-Once lock
        protected $_isResourceSetupData        = false;
        protected $_isResourceSetupPaginator   = false;
        protected $_isResourceSetupTableFormat = false;

    // Parse-Once lock
        protected $_isParsedXhtmlPaginator     = false;
        protected $_isParsedXhtmlTableData     = false;
        protected $_isParsedXhtmlTableHeader   = false;
        protected $_isParsedXhtmlTableFooter   = false;
        protected $_isParsedXhtmlTableExport   = false;

        protected $_isParsedExcelTableData     = false;
        protected $_isParsedExcelTableHeader   = false;

        protected $_isParsedJsonTableData = false;

    // Include-Once lock
       protected static $_isJsIncluded = false;

    // MVC Instance
        protected $_mvcInstance;
        protected $_request;
        protected $_response;
        protected $_view;
        protected $_layout;

    // List of instance
        protected static $_instances = array();
/*
    // Excel style
        const EXCEL_STYLE_HEADER      = 'header';
        const EXCEL_STYLE_BODY_NORMAL = 'normal';
        const EXCEL_STYLE_BODY_CENTER = 'center';
        const EXCEL_STYLE_BODY_CURRENCY_0 = 'currency-0';
        const EXCEL_STYLE_BODY_CURRENCY_1 = 'currency-1';
        const EXCEL_STYLE_BODY_DATE       = 'date';

        const EXCEL_STYLE_LAST_BODY_NORMAL = 'last-normal';
        const EXCEL_STYLE_LAST_BODY_CENTER = 'last-center';
        const EXCEL_STYLE_LAST_BODY_CURRENCY_0 = 'last-currency-0';
        const EXCEL_STYLE_LAST_BODY_CURRENCY_1 = 'last-currency-1';
        const EXCEL_STYLE_LAST_BODY_DATE       = 'last-date';

        public static $defaultExcelStyles = array(
            self::EXCEL_STYLE_HEADER => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_SOLID,
                    'color' => array('rgb' => 'd3d3d3')
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 8,
                    'bold' => true,
                    'color' => array('rgb' => '333333')
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER
                ),
                'borders' => array(
                    'allborders' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                )
            ),
            self::EXCEL_STYLE_BODY_NORMAL => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                )
            ),
            self::EXCEL_STYLE_BODY_CENTER => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                )
            ),
            self::EXCEL_STYLE_BODY_CURRENCY_0 => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => '_(* #,##0_);_(* \(#,##0\);_(* "-"??_);_(@_)'
                )
            ),
            self::EXCEL_STYLE_BODY_CURRENCY_1 => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)'
                )
            ),
            self::EXCEL_STYLE_BODY_DATE => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => 'DD/MM/YYYY'
                )
            ),

            self::EXCEL_STYLE_LAST_BODY_NORMAL => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'bottom' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                )
            ),
            self::EXCEL_STYLE_LAST_BODY_CENTER => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'bottom' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                )
            ),
            self::EXCEL_STYLE_LAST_BODY_CURRENCY_0 => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'bottom' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => '_(* #,##0_);_(* \(#,##0\);_(* "-"??_);_(@_)'
                )
            ),
            self::EXCEL_STYLE_LAST_BODY_CURRENCY_1 => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'bottom' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)'
                )
            ),
            self::EXCEL_STYLE_LAST_BODY_DATE => array(
                'fill' => array(
                    'type'  => PHPExcel_Style_Fill::FILL_NONE
                ),
                'font' => array(
                    'name' => 'Times New Roman',
                    'size' => 10
                ),
                'alignment' => array(
                    'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
                    'vertical'   => PHPExcel_Style_Alignment::VERTICAL_CENTER,
                ),
                'borders' => array(
                    'top' => array(
                        'style' => PHPExcel_Style_Border::BORDER_DASHED,
                        'color' => array('rgb' => 0)
                    ),
                    'left' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'bottom' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    ),
                    'right' => array(
                        'style' => PHPExcel_Style_Border::BORDER_THIN,
                        'color' => array('rgb' => 0)
                    )
                ),
                'numberformat' => array(
                    'code' => 'DD/MM/YYYY'
                )
            ),
        );
 */
    // CONSTRUCTOR
    /**
     * @param string $gridName
     * @param Zend_Db_Select|Zend_Db_Table_Abstract $data
     * @param mixed $formatTableColumn
     * @param mixed $autoResolveXmlHttpRequest
     */
    public function __construct($gridName, $autoResolveXmlHttpRequest=true, $data=null, $formatTableColumn=null) {
        if(!$gridName) {
            throw new Zend_Exception('Every one\'s got their own name, so has a Grid.');
        }

        // Set name
        $this->setName((string)$gridName);

        // Get MVC Instance
        $mvcInstance = Zend_Controller_Front::getInstance();
            $this->_mvcInstance = $mvcInstance;
            $this->_request     = $mvcInstance->getRequest();
            $this->_response    = $mvcInstance->getResponse();
            $bootstrap          = $mvcInstance->getParam('bootstrap');
                $this->_view        = $bootstrap->getResource('view');
                $this->_layout      = $bootstrap->getResource('layout');

        $this->_viewScriptPath = realpath(dirname(__FILE__));

        // Init data
            $this->enableAutoResolveXmlHttpRequest($autoResolveXmlHttpRequest);
            $this->_formatTableColumn = $formatTableColumn;
            if($data) { $this->setData($data); }

        $this->init();

        // Add to instance list - enable dumping
        self::addInstance($this);
    }
    protected function init() {} /* To be extended by subclass */

    // PUBLIC CONTROL
        public function applySearch(Apps_Plugin_Grid_Search_Abstract $search) {
            $this->_resourceSearch = $search;
            $search->applyCriteria($this);
            return $this;
        }
        public function resolveXmlHttpRequest($resolveOnce=true) {
        	if(!$this->_enableAutoResolveXmlHttpRequest) {
        		$this->enableAutoResolveXmlHttpRequest(true);
        	}
            $this->_ajaxHandler->resolveXmlHttpRequest($resolveOnce);
            return $this;
        }
        public function enableAutoResolveXmlHttpRequest($enable=true) {
            $ajaxHandler = $this->_ajaxHandler;

            if($enable&& !$this->_enableAutoResolveXmlHttpRequest) {
            	$ajaxHandler = new Apps_Plugin_Grid_Ajax_Handler($this);
                $this->_mvcInstance->registerPlugin($ajaxHandler);

                $this->_ajaxHandler                     = $ajaxHandler;
                $this->_enableAutoResolveXmlHttpRequest = true;
            } else if(!$enable && !$this->_enableAutoResolveXmlHttpRequest) {
                $this->_mvcInstance->unregisterPlugin($ajaxHandler);

                $this->_ajaxHandler                     = null;
                $this->_enableAutoResolveXmlHttpRequest = false;
            }
            return $this;
        }
        public function disableLayouts($disableLayout=true, $disableView=true) {
            if($disableLayout === true) {
                $this->_layout->disableLayout();
            } else if($disableLayout === false) {
                $this->_layout->enableLayout();
            }
            if(is_bool($disableView)) {
                $this->_mvcInstance->setParam('noViewRenderer', $disableView);
            }
            return $this;
        }

    // SETTER
       public function setName($name) {
           $this->_gridName = $this->_gridNamePrefix . ucfirst($name);
           return $this;
       }
       public function setData($data) {
           if($data instanceof Apps_Db_Table_Abstract)      { $data = Zend_Db_Table_Abstract::getDefaultAdapter()->select()->from($data->getName()); }
           else if($data instanceof Zend_Db_Table_Abstract) { $data = $data->select(); }
           if($data instanceof Zend_Db_Select) {
               $this->_resourceData = $data;
           }
           return $this;
       }
       public function setControlType($control) {
           if(!in_array($control, $this->_gridControlList)) {
               throw new Zend_Exception('No support for ' . $control .' control type found.');
           }
           $this->_gridControl = $control;
           return $this;
       }
       public function setRowPerPageOptions(Array $options) {
           $this->_rowPerPageOptions = $options;
           return $this;
       }
       public function setColumnHiddenMask($mask) {
       // @todo
            if(is_string($mask)) {
                $this->_resourceColumnHiddenMask = $mask;
            }
            return $this;
       }
       public function setCheckboxDataField($dataField) {
           $this->_checkboxDataField = $dataField;
           return $this;
       }
       /**
        *
        *    title
        *    disabled        [optional]
        *    dataField       [optional]
        *    sortDisabled    [optional]
        *    sortField       [optional, default=dataField]
        *    width           [optional]
        *    html            [optional, default=$rs[dataField]]
        *    class           [optional]
        *
        * @param array $formatTableColumn
        * @return $this
        */
       public function setTableColumnFormat($formatTableColumns) {
           $this->_formatTableColumn = $formatTableColumns;
           return $this;
       }
       public function addTableColumnFormat($formatTableColumn, $key=null) {
           if($key) {
               $this->_formatTableColumn[$key] = $formatTableColumn;
           } else {
               $this->_formatTableColumn[] = $formatTableColumn;
           }
           return $this;
       }
       public function setPaginationOptions($paginationOptions) {
           $this->_paginationOptions = array_merge($this->_paginationOptions, $paginationOptions);
           return $this;
       }
       public function disablePagination() {
           $this->_paginationOptions['defaultItemCountPerPage'] = null;
           if($this->_isResourceSetupPaginator) {
               $this->_resourcePaginator->setItemCountPerPage(null);
           }
           return $this;
       }
       public function disableSort() {
           $this->_resourceAllowSort = false;
           return $this;
       }
       public function isJustNakedTable() {
           return $this->disableSort()->disablePagination();
       }
       public function setTableCaption($caption) {
           $this->_resourceTableCaption = $caption;
           return $this;
       }
       public function setTableWidth($width) {
           $this->_resourceTableWidth = $width;
           return $this;
       }
        /**
         * Set TableRow tag attribute
         * @param array $attr
         * @param function|true(always) $fn
         */
        public function addTrAttribute($attr, $fn=true, $overwrite=false) {
        	$key = serialize($attr);
            if(null === $fn) {
                return $this;
            } else if(isset($this->_resourceTrAttributes[$key])) {
            	// @todo merge, prepend ...
            	if(!$overwrite) {
                    return $this; // @todo: should throw overwrite exception
            	}
            }
            $this->_resourceTrAttributes[$key] = array($attr, $this->_parseTrAttributeCondition($fn));

            return $this;
        }
        public function removeAttrAttribute($attr, $errorOnNotFound=false) {
            $key = serialize($attr);
            if(!isset($this->_resourceTrAttributes[$key]) && !$errorOnNotFound) {
                return $this; // @todo: should throw not found exception
            }
            unset($this->_resourceTrAttributes[$key]);

            return $this;
        }
        /**
         * Clear all preset attributes
         * @param array $attrs array(array($attr=>$fn))
         */
        public function setTrAttributes($attrs) {
            $this->_resourceTrAttributes = null;
            foreach ($attrs as $attr => $fn) {
                $this->addTrAttribute($attr, $fn);
            }
        }
        private function _parseTrAttributeCondition($fn) {
            if(true === $fn) { $fn = create_function('', 'return true;'); }

            if(!is_callable($fn)) { $fn = null; }
            return $fn;
        }

    // GETTER
        // GET PROPERTY
        public function getFormatExcelTableHeader() { $this->setupResourceTableFormat(); return $this->_formatExcelTableHeader; }
        public function getFormatExcelTableData()   { $this->setupResourceTableFormat(); return $this->_formatExcelTableData; }

        public function getFormatTableHeader() { $this->setupResourceTableFormat(); return $this->_formatTableHeader; }
        public function getFormatTableData()   { $this->setupResourceTableFormat(); return $this->_formatTableData; }
        public function getFormatTableColumn() { return $this->_formatTableColumn; }


        public function getName()     { return $this->_gridName; }
        public function getRequest()  { return $this->_request;  }
        public function getResponse() { return $this->_response; }
        public function getView()     { return $this->_view;     }
        public function getPaginationParams() {
           return get_object_vars($this->_resourcePaginator->getPages(
                                      $this->_paginationOptions['scrollingStyle']));
        }
        public function getData()     { return $this->_resourceData; }
        public function isEmpty()     {
            $this->setupResourcePaginator();
            return !$this->_resourcePaginator->getTotalItemCount();
        }
        public function getTableCaption() {return $this->_resourceTableCaption; }
        public function getTableColumnsCount(){return count($this->getFormatTableHeader());}
        public function getExcelColumnsCount(){return count($this->getFormatExcelTableHeader());}
        public function enableExport() {
            // @todo: Why lay it here? lazy
            // Resolve export if needed
            if($exportType = $this->_request->getParam('grid-export')) {
                $this->disableLayouts(true, true);
                $this->export($exportType);
            }
            $this->_enableExport = true;
        }
        public function getExportKeywordTable() {
            return self::EXPORT_KEYWORD_TABLE;
        }

       // GET XHTML
           public function getXhtmlSearchBox($renderType=Apps_Plugin_Grid_Search_Abstract::RENDER_BOTH) {
               return $this->_resourceSearch
                      ? $this->_resourceSearch->getXhtml($renderType)
                      : '';
           }
           public function getXhtml($tableAttr=array(), $theadAttr=array(), $tbodyAttr=array(), $tfootAttr=array(),
                                    $width=null       , $includeJS=true   , $includeForm=false) {
               $xhtml = $this->getXhtmlTable($tableAttr, $theadAttr, $tbodyAttr, $tfootAttr, $width);

               if($includeJS)   { $xhtml .= '<script>' . $this->getJavascript() . '</script>'; }
               if($includeForm) { $xhtml = '<form method="post">' . $xhtml . '</form>'; }

               return $xhtml;
           }
           public function getXhtmlTable($tableAttr=array(), $theadAttr=array(), $tbodyAttr=array(), $tfootAttr=array(), $width=null) {
               $gridName   = $this->_gridName;
               $xhtmlTable = '' ;

               $isEmpty = $this->isEmpty();
               // Search box
               $xhtmlTable = $this->getXhtmlSearchBox() . $xhtmlTable;

               if($width) {$this->setTableWidth($width);}
               $width = $this->_resourceTableWidth;

                   // Export
                   if(!$isEmpty && $this->_enableExport) {
                       $xhtmlTable .= '<div id="' . $gridName . '-export-bar"'
                                    . ' class="export-bar">'
                                    . $this->getXhtmlTableExport()   . '</div>';
                   }
                   $xhtmlTable .= $this->getXhtmlPaginationControl() . '<div class="clear"></div>';

                   // Table
                   $xhtmlTable .= '<table name="' . $gridName . '" id="' . $gridName . '"'
                                . $this->_parseHtmlAttribute($tableAttr)
                                . '>';

                       $xhtmlTable .= '<thead id="' . $gridName . '-thead"'
                                    . $this->_parseHtmlAttribute($theadAttr)
                                    . '>' . $this->getXhtmlTableHeader() . '</thead>';

                       $xhtmlTbody = $isEmpty
                                     ? '<tr><td colspan="0" title="Danh sách trống">&nbsp;</td></tr>'
                                     : $this->getXhtmlTableData();

                       $xhtmlTable .= '<tbody id="' . $gridName . '-tbody"'
                                    . $this->_parseHtmlAttribute($tbodyAttr)
                                    . '>' . $xhtmlTbody . '</tbody>';
                       /* // Commented - beauty issue
                       $_xhtmlTable .= '<tfoot id="' . $gridName . '-tfoot"'
                                    . $this->_parseHtmlAttribute($tfootAttr)
                                    . '>' . $this->getXhtmlTableFooter() . '</tfoot>';
                       */
                   $xhtmlTable .= '</table>';

                   // Table width scroll
                   if(null !== $width) {
                       $xhtmlTable = '<div style="overflow:auto">
                                          <div style="width:' . $width . '">'
                                          . $xhtmlTable . '</div></div>';
                   }

                   $xhtmlTable .= $this->getXhtmlPaginationControl();

                   // Table caption
                   if($c = $this->_resourceTableCaption) {
                       $xhtmlTable = '<h3 class="table-caption">' . $c . '</h3>' . $xhtmlTable;
                   }
               $xhtmlTable = '<div id="' . $gridName . '-container">' . $xhtmlTable . '<div class="clear"></div></div>';

               return $xhtmlTable;
           }
           public function getXhtmlTableHeader() {
               if(!$this->_isParsedXhtmlTableHeader) { $this->_parseXhtmlTableHeader(); }
               return $this->_xhtmlTableHeader;
           }
           public function getXhtmlTableFooter() {
               if(!$this->_isParsedXhtmlTableFooter) { $this->_parseXhtmlTableFooter(); }
               return $this->_xhtmlTableFooter;
           }
           public function getXhtmlTableData() {
               if(!$this->_isParsedXhtmlTableData) { $this->_parseXhtmlTableData(); }
               return $this->_xhtmlTableData;
           }
           /**
            * @return string
            */
           public function getXhtmlTableExport() {
               if(!$this->_isParsedXhtmlTableExport) { $this->_parseXhtmlTableExport(); }
               return $this->_xhtmlTableExport;
           }
           public function getXhtmlPaginationControl($hideOnSingle=true) {
               if(!$this->_isParsedXhtmlPaginator) { $this->_parseXhtmlPaginationControl($hideOnSingle); }
               $xhtml = $this->_xhtmlPaginationControl;
               return $xhtml;
           }
           public function getJavascript($scriptTagWrap=false) {
               $gridName = $this->_gridName;

               $js  = self::getBaseJavascript(true);
               $js .= 'window.appGrid_dataUpdate_' . $gridName
                      . ' = function(){appGrid_dataUpdate("' . $gridName . '", "' . $this->_gridControl . '")};';
               if($scriptTagWrap) {
                   $js = '<script>' . $js . '</script>';
               }
               return $js;
           }
           public static function getBaseJavascript($includeOnce=true) {
               if($includeOnce && self::$_isJsIncluded) { return ''; }
               // Development js - NOT OUTPUT, plz see minified version below
               $js = 'window.appGrid_dataUpdate = function(g, t) {
                          t = t || "ajax";

                          var _g = $("#" + g)[0];

                          if("ajax" == t) {
                              var tbody = _g.cache_tbody || (_g.cache_tbody = $("tbody#" + g + "-tbody"));
                              var sb    = _g.cache_sb    || (_g.cache_sb    = $("#" + g + "-sb"));
                              var sc    = _g.cache_sc    || (_g.cache_sc    = $("#" + g + "-sc"));
                              var s     = _g.cache_s     || (_g.cache_s     = $("#" + g + "-s"));

                              var st    = _g.cache_st    || (_g.cache_st    = $("."      + g + "-st"));
                              var pag   = _g.cache_pag   || (_g.cache_pag   = $("ul."    + g + "-pp-picker"));
                              var pr    = _g.cache_pr    || (_g.cache_pr    = $("." + g + "-pr"));
                              var pp    = _g.cache_pp    || (_g.cache_pp    = $("." + g + "-pp"));

                              tbody.css("opacity", "0.5");
                              pag.css("opacity", "0.5");
                              ajaxload_loading.show();                               $.ajax("", {
                                  data: {
                                      "g"  : g,
                                      "pr" : pr.val(), "pp" : pp.val(),
                                      "sb" : sb.val(), "sc" : sc.val(),
                                      "s_keyword": $("search-input").val()
                                  },
                                  type: "post",
                                  dataType: "json",
                                  success: function(json) {
                                      var d = json.d;
                                      var xhtmlTbody = [];
                                      for(r in d) { r = d[r]; row = r[0];
                                          xhtmlTbody.push("<tr");
                                          xhtmlTbody.push(r[1]);
                                          xhtmlTbody.push(">");
                                          for(c in row) {
                                              c = row[c];
                                              xhtmlTbody.push("<td class=\"");
                                              xhtmlTbody.push(c[0]);
                                              xhtmlTbody.push("\">");
                                              xhtmlTbody.push(c[1]);
                                              xhtmlTbody.push("</td>");
                                          }
                                          xhtmlTbody.push("</tr>");
                                      }
                                      tbody.fadeTo(100, 0).html(xhtmlTbody.join("")).fadeTo(150, 1);

                                      var p = json.p;
                                      var xhtmlPag = [];
                                      xhtmlPag.push("<li class=\"prev");
                                      xhtmlPag.push(p.p ? "\">" : " disabled\">");
                                      xhtmlPag.push("<a href=\"javascript:void(0)\" rel=\"");
                                      xhtmlPag.push(p.p);
                                      xhtmlPag.push("\">&larr;</a></li>");

                                      r = p.r, a = p.a;
                                      for(i in r) {
                                          xhtmlPag.push("<li");
                                          xhtmlPag.push(i == a ? " class=\"active\">" : ">");
                                          xhtmlPag.push("<a href=\"javascript:void(0)\" rel=\"");
                                          xhtmlPag.push(r[i]);
                                          xhtmlPag.push("\">");
                                          xhtmlPag.push(r[i]);
                                          xhtmlPag.push("</a></li>");
                                      }

                                      xhtmlPag.push("<li class=\"next");
                                      xhtmlPag.push(p.n ? "\">" : " disabled\">");
                                      xhtmlPag.push("<a href=\"javascript:void(0)\" rel=\"");
                                      xhtmlPag.push(p.n);
                                      xhtmlPag.push("\">&rarr;</a></li>");

                                      pag.html(xhtmlPag.join("")).css("opacity", 1);
                                      st.html([p.f, " - ", p.l ," / <b>", p.t, "</b>"].join(""));
                                  },
                                  error: function(e) { console.log("An error occured,plz try again") },
                                  complete: function(e) {ajaxload_loading.hide()}
                              });
                          } else if ("classic" == t)  {
                              $("#" + g).parents("form").submit();
                          }
                        }
                        $(function(){
                            $("a.appGrid-sort-picker").live("click", function(){
                                var g  = $(this).attr("data-grid"), _g = $("#" + g)[0];
                                var sb = _g.cache_sb || (_g.cache_sb = $("#" + g + "-sb"));
                                var sc = _g.cache_sc || (_g.cache_sc = $("#" + g + "-sc"));

                                var c  = sc.val(), s  = this.rel;
                                sb.val((sb.val()===c && s===c) ? "" : c);
                                sc.val(s);

                                window["appGrid_dataUpdate_" + g]();
                            });
                            $(".appGrid-pr-picker").live("change", function(){
                                var g  = $(this).attr("data-grid"), _g = $("#" + g)[0];
                                var pr = _g.cache_pr || (_g.cache_pr = $("." + g + "-pr"));

                                pr.val(this.value);

                                window["appGrid_dataUpdate_" + g]();
                            });
                            $(".appGrid-pp-picker").live("click", function(e){
                                var g  = $(this).attr("data-grid"), _g = $("#" + g)[0];
                                var pp = _g.cache_pp || (_g.cache_pp = $("." + g + "-pp"));

                                pp.val(e.target.rel);

                                window["appGrid_dataUpdate_" + g]();
                            });
                        });';

                // Minified-version
                $js = 'window.appGrid_dataUpdate=function(b,d){d=d||"ajax";var e=$("#"+b)[0];if("ajax"==d){var f=e.cache_tbody||(e.cache_tbody=$("tbody#"+b+"-tbody"));var g=e.cache_sb||(e.cache_sb=$("#"+b+"-sb"));var h=e.cache_sc||(e.cache_sc=$("#"+b+"-sc"));var j=e.cache_s||(e.cache_s=$("#"+b+"-s"));var k=e.cache_st||(e.cache_st=$("."+b+"-st"));var l=e.cache_pag||(e.cache_pag=$("ul."+b+"-pp-picker"));var m=e.cache_pr||(e.cache_pr=$("."+b+"-pr"));var n=e.cache_pp||(e.cache_pp=$("."+b+"-pp"));f.css("opacity","0.5");l.css("opacity","0.5");ajaxload_loading.show();$.ajax("",{data:{g:b,pr:m.val(),pp:n.val(),sb:g.val(),sc:h.val(),s_keyword:$("search-input").val()},type:"post",dataType:"json",success:function(b){var d=b.d;var e=[];for(r in d){r=d[r];row=r[0];e.push("<tr");e.push(r[1]);e.push(">");for(c in row){c=row[c];e.push(\'<td class="\');e.push(c[0]);e.push(\'">\');e.push(c[1]);e.push("</td>")}e.push("</tr>")}f.fadeTo(100,0).html(e.join("")).fadeTo(150,1);var g=b.p;var h=[];h.push(\'<li class="prev\');h.push(g.p?\'">\':\' disabled">\');h.push(\'<a href="javascript:void(0)" rel="\');h.push(g.p);h.push(\'">←</a></li>\');r=g.r,a=g.a;for(i in r){h.push("<li");h.push(i==a?\' class="active">\':">");h.push(\'<a href="javascript:void(0)" rel="\');h.push(r[i]);h.push(\'">\');h.push(r[i]);h.push("</a></li>")}h.push(\'<li class="next\');h.push(g.n?\'">\':\' disabled">\');h.push(\'<a href="javascript:void(0)" rel="\');h.push(g.n);h.push(\'">→</a></li>\');l.html(h.join("")).css("opacity",1);k.html([g.f," - ",g.l," / <b>",g.t,"</b>"].join(""))},error:function(a){console.log("An error occured,plz try again")},complete:function(a){ajaxload_loading.hide()}})}else if("classic"==d){$("#"+b).parents("form").submit()}};$(function(){$("a.appGrid-sort-picker").live("click",function(){var a=$(this).attr("data-grid"),b=$("#"+a)[0];var c=b.cache_sb||(b.cache_sb=$("#"+a+"-sb"));var d=b.cache_sc||(b.cache_sc=$("#"+a+"-sc"));var e=d.val(),f=this.rel;c.val(c.val()===e&&f===e?"":e);d.val(f);window["appGrid_dataUpdate_"+a]()});$(".appGrid-pr-picker").live("change",function(){var a=$(this).attr("data-grid"),b=$("#"+a)[0];var c=b.cache_pr||(b.cache_pr=$("."+a+"-pr"));c.val(this.value);window["appGrid_dataUpdate_"+a]()});$(".appGrid-pp-picker").live("click",function(a){var b=$(this).attr("data-grid"),c=$("#"+b)[0];var d=c.cache_pp||(c.cache_pp=$("."+b+"-pp"));d.val(a.target.rel);window["appGrid_dataUpdate_"+b]()})});';
                self::$_isJsIncluded = true;
                return $js;
           }
       // GETTER JSON
           public function getJsonTableData($jsonEncode=true) {
               if(!$this->_isParsedJsonTableData) { $this->_parseJsonTableData($jsonEncode); }
               return $this->_jsonTableData;
           }

    // PARSER
        // PARSER XHTML
        private function _parseXhtmlSorter() {
        	$rq = $this->_request;

        	$gridName = $this->_gridName;
            $checkbox = $this->_checkboxDataField;

            $xhtmlTableSorter = '<tr>';

            // th
            $formatTableHeader = $this->getFormatTableHeader();
            foreach($formatTableHeader as $thFormat) {
                $xhtmlTableSorter .= '<th';
                $xhtmlTableSorter .= isset($thFormat['width'])
                                     ? ' width="' . $thFormat['width'] . '">'
                                     : '>';

                $xhtmlTableSorter .= isset($thFormat['sortField'])
                                     ? '<a class="appGrid-sort-picker" data-grid="' . $gridName
                                       . '" href="##" rel="' . $thFormat['sortField'] . '">'
                                       . $thFormat['title']
                                       . '</a>'
                                     : $thFormat['title'];

                $xhtmlTableSorter .= '</th>';
            }

            if($checkbox) {
                $xhtmlTableSorter .= '<th width="1"><input type="checkbox" class="'
                                   . $this->_elementClass['checkboxAll']
                                   . '"/></th>';
            }

            $xhtmlTableSorter .= '</tr>';

            return $xhtmlTableSorter;
        }
        private function _parseXhtmlTableHeader() {
            $xhtmlTableHeader  = $this->_parseXhtmlSorter();

            $gridName = $this->_gridName;
            // Sorting script
        	$rq = $this->_request;
            $xhtmlTableHeader .= '<input type="hidden" id="' . $gridName . '-sb" name="sb" value="'
                                  . $rq->getParam('sb') . '"/>
                                  <input type="hidden" id="' . $gridName . '-sc" name="sc" value="'
                                  . $rq->getParam('sc') . '"/>';

            $this->_xhtmlTableHeader = $xhtmlTableHeader;
            $this->_isParsedXhtmlTableHeader = true;
        }
        private function _parseXhtmlTableFooter() {
            $xhtmlTableFooter = $this->_parseXhtmlSorter();

            $this->_xhtmlTableFooter = $xhtmlTableFooter;
            $this->_isParsedXhtmlTableFooter = true;
        }
        private function _parseXhtmlTableData() {
            $this->setupResourcePaginator();

            $checkbox = $this->_checkboxDataField;

            $formatTableData = $this->getFormatTableData();
            $classCheckbox   = $this->_elementClass['checkbox'];
            $nameCheckbox    = $this->_elementName['checkbox'];

            $index = 0;

            $trAttributes = $this->_resourceTrAttributes;

            $xhtmlTableData = '';

            foreach ($this->_resourcePaginator as $dataRow) {
                $xhtmlTableData .= '<tr';
                foreach ($trAttributes as $trAttribute) {
                	if(is_callable($trAttrFn = $trAttribute[1]) && $trAttrFn($dataRow, $index)) {
	                    $xhtmlTableData .= $this->_parseHtmlAttribute($trAttribute[0]);
	                }
                }
                $xhtmlTableData .= '>';

                foreach($formatTableData as $tdFormat) {
                    $xhtmlTableData .= '<td';
                    if($tdClass = $tdFormat[0]) {
                        $xhtmlTableData .= ' class="' . $tdClass($dataRow, $index) . '"';
                    }
                    $xhtmlTableData .= '>';
                        if($tdXhtml = $tdFormat[1]) {
                            $xhtmlTableData .= $tdXhtml($dataRow, $index);
                        }
                    $xhtmlTableData .= '</td>';
                }

                if(is_string($checkbox)) {
                    $xhtmlTableData .= '<td><input type="checkbox"'
                                     . ($classCheckbox ? ' class="' . $classCheckbox . '"' : '')
                                     . ($nameCheckbox  ? ' name="'  . $nameCheckbox  . '"' : '')
                                     . '" value="'
                                     . (isset($dataRow[$checkbox]) ? $dataRow[$checkbox] : $index)
                                     . '"/></td>';
                }

                $xhtmlTableData .= '</tr>';
                ++$index;
            }

            $this->_xhtmlTableData = $xhtmlTableData;
            $this->_isParsedXhtmlTableData = true;
        }
        private function _parseXhtmlTableExport() {
            $rq = $this->_request;

            $classExportPicker = 'export-picker';

            $exportType = array(
                            'xls-all'      => 'Excel 2003',
                            'xlsx-all'     => 'Excel 2007',

                            'xls'          => 'Excel 2003 - 1 page',
                            'xlsx'         => 'Excel 2007 - 1 page',

                            'xls-all-raw'  => 'Excel 2003 - raw',
                            'xlsx-all-raw' => 'Excel 2007 - raw',

                            'csv'          => 'CSV'
                          );

            $xhtmlTableExport = '';

            foreach($exportType as $ext => $type) {
                $xhtmlTableExport .= sprintf('<li><a rel="%s" class="' . $classExportPicker . '" href="##">%s</a></li>', $ext, $type);
            }

            $xhtmlTableExport = '<div class="btn-group"><a class="btn dropdown-toggle" data-toggle="dropdown" href="##"><i class="icon-print"></i>&nbsp;<span class="caret"></span></a>
                                 <ul class="dropdown-menu">'
                                 . $xhtmlTableExport
                                 . '</ul></div><input type="hidden" id="' . $this->getName(). '-export-type" name="grid-export"/> <script>$(function(){$(".' . $classExportPicker. '").live("click", function(){ var msg="Quá trình kết xuất có thể mất nhiều thời gian tùy thuộc vào lượng dữ liệu, \nBạn có muốn tiếp tục kết xuất?";if(!confirm(msg)){return true;}$("#' . $this->getName() . '-export-type").val($(this).attr("rel")).parents("form").submit(); })})</script>';

            $this->_xhtmlTableExport = $xhtmlTableExport;
            $this->_isParsedXhtmlTableExport = true;
        }
        private function _parseXhtmlPaginationControl($hideOnSingle) {
            $this->setupResourcePaginator();

            $paginationOptions = $this->_paginationOptions;

            // Get pagination params
                $paginationParams = $this->getPaginationParams();
                $paginationParams['gridName'] = $this->_gridName;
                $paginationParams['rowPerPageOptions'] = $this->_rowPerPageOptions;
                $paginationParams['hideOnSingle'] = $hideOnSingle && (count($paginationParams['pagesInRange']) < 2);

                if($predefinedPaginationParams = $paginationOptions['params']) {
                    $paginationParams = array_merge(
                                            $paginationParams,
                                            (array) $predefinedPaginationParams);
                }

            $this->_view->addScriptPath($this->_viewScriptPath);
            $this->_xhtmlPaginationControl = $this->_view->partial(
                                                 $paginationOptions['partial'],
                                                 $paginationParams);

            $this->_isParsedXhtmlPaginator = true; // Parsing lock
        }

        // PARSER JSON
        private function _parseJsonTableData($jsonEncode=true) {
            $this->setupResourcePaginator();

            $checkbox = $this->_checkboxDataField;

            $formatTableData = $this->_formatTableData;
            $classCheckbox   = $this->_elementClass['checkbox'];
            $nameCheckbox    = $this->_elementName['checkbox'];

            $table = array();
            $index = 0;
            foreach ($this->_resourcePaginator as $dataRow) {
                $tr = array();
	                // Data
	                foreach($formatTableData as $tdFormat) {
	                    $tr []= array(
	                                (($tdClass = $tdFormat[0]) ? $tdClass($dataRow, $index) : ''),
	                                (($tdXhtml = $tdFormat[1]) ? $tdXhtml($dataRow, $index) : ''));
	                }
	                // Checkbox
	                if(is_string($checkbox)) {
	                    $tr []= array('',
	                            '<input type="checkbox"'
	                            . ($classCheckbox ? ' class="' . $classCheckbox . '"' : '')
	                            . ($nameCheckbox  ? ' name="'  . $nameCheckbox  . '"' : '')
	                            . 'value="'
	                            . (isset($dataRow[$checkbox]) ? $dataRow[$checkbox] : $index)
	                            . '"/>');
	                }

                // Tr Attribute
                $trAttr = '';
                foreach ($this->_resourceTrAttributes as $trAttribute) {
                    if(is_callable($trAttrFn = $trAttribute[1]) && $trAttrFn($dataRow, $index)) {
                        $trAttr .= $this->_parseHtmlAttribute($trAttribute[0]);
                    }
                }

                $table []= array($tr, $trAttr);
                ++$index;
            }

            $this->_jsonTableData = $jsonEncode
                                    ? json_encode($table)
                                    : $table;
            $this->_isParsedJsonTableData = true;
        }

    // RESOURCES SETUP

    // Public method
        public function setupResourceData() {
            if(!$this->_isResourceSetupData) { $this->_setupResourceData(); }
            return $this;
        }
        public function setupResourcePaginator() {
            if(!$this->_isResourceSetupPaginator) { $this->_setupResourcePaginator(); }
            return $this;
        }
        public function setupResourceTableFormat() {
            if(!$this->_isResourceSetupTableFormat) { $this->_setupResourceTableFormat(); }
            return $this;
        }
    // Private process
        private function _setupResourceTableFormat() {
            $f = $this->_formatTableColumn;

            if(null == $f || self::COLUMNS_IN_SELECT == $f && $this->getData()) {
                $f = array();
                $columns = $this->getData()->getPart(Zend_Db_Select::COLUMNS);
                foreach ($columns as $column) {
                    if($c = $column[2]) {
                        $f[] = array(
                                   'title'     => $c,
                                   'dataField' => $c
                               );
                    }
                }
                $this->setTableColumnFormat($f);
            }

            if(!is_array($this->_formatTableColumn)) {
                throw new Zend_Exception('Can not process with wrong type Table-Column-Format. Table-Column-Format must be set as an array');
            }

            $formatTableHeader  = array(); $formatExcelTableHeader = array();
            $formatTableData    = array(); $formatExcelTableData   = array();
            $sortableField      = array(); $sortableFieldIndex = 0;

            $gridSortEnabled = $this->_resourceAllowSort;

            // Show/Hide column, in order to save user show off col list
            $columnHiddenMask = $this->_resourceColumnHiddenMask;

            $formatTableColumn  = $this->_formatTableColumn;
            foreach ($formatTableColumn as $i => $c) {
                if(isset($columnHiddenMask[$i]) && $columnHiddenMask[$i]) { continue; }

                $enable = isset($c['disabled'])
                          ? !$c['disabled']
                          : true;

                if($enable) { // Column enabled
                    $dataField = isset($c['dataField'])
                                 ? $c['dataField']
                                 : null;

                    $sortField = null;
                    if($gridSortEnabled) {
                    // Only when grid enable sort
                        $sortField = isset($c['sortField'])
                                     ? $c['sortField']
                                     : $dataField;
                    }
                    $excelEnabled = !isset($c['excelDisabled']) || !$c['excelDisabled'];

                    // Table Header
                    // $formatTableHeader = array($columnFormat, $columnFormat ...);
                    // $columnFormat = array('title' => '...', 'sortField' => '...');
                        $th = array('title' => $c['title']);

                        if($sortField) {
                            if(!isset($c['sortDisabled']) || !$c['sortDisabled']) {
                                $sortableField[$sortableFieldIndex] = $sortField;
                                $th['sortField'] = $sortableFieldIndex++;
                            }
                        }
                        if(isset($c['width'])) { $th['width'] = $c['width']; }

                        $formatTableHeader []= $th;
                        if($excelEnabled) { $formatExcelTableHeader []= $th; }

                    // Table Data
                    // $formatTableData = array($columnFormat1, $columnFormat2 ... )
                    // $columnFormat1 = ['class1', function($rs, $index) {return '...'}]

                        $tdFn = null;
                        if(isset($c['html'])) {
                            $html = $c['html'];

                            if(is_callable($html)) {
                                $tdFn = $html;
                            } else if(is_string($html)) {
                                $tdFn = create_function('$dataRow, $index', 'return \'' . $html . '\';');
                            } else if(is_array($html)) {
                                $fn   = is_array($html[1])
                                        ? $html[1]
                                        : array((string)$html[1]);
                                $fn   = '$dataRow[\'' . implode('\'], $dataRow[\'', $fn) . '\']';
                                $fn   = 'return sprintf(\'' . $html[0] . '\', '. $fn . ');';

                                $tdFn = create_function('$dataRow', $fn);
                            }
                        } else if($dataField) {
                            $tdFn = create_function('$dataRow', 'return $dataRow[\'' . $dataField . '\'];');
                        }

                        $tdClass = null;
                        if(isset($c['class'])) {
                            $class = $c['class'];
                            if(is_string($class)) {
                                $tdClass = create_function('', 'return \'' . $class . '\';');
                            } else if(is_callable($class)) {
                                $tdClass = $class;
                            }
                        }

                    $formatTableData []= array($tdClass, $tdFn);

                    if($excelEnabled) {
                        // Excel Table Data
                        // $formatExcelTableData = array($columnFormat1, $columnFormat2 ... )
                        // $columnFormat1 = [function($rs, $index) {return '...'}, predefinedStyle]

                        $tdExcelFn = null;
                        if(isset($c['excel'])) {
                            $excel = $c['excel'];

                            if(is_callable($excel)) {
                                $tdExcelFn = $excel;
                            } else if(is_string($excel)) {
                                $tdExcelFn = create_function('$dataRow, $row, $col, $indexRow, $indexCol', 'return \'' . $excel . '\';');
                            } else if(is_array($excel)) {
                                $fn   = is_array($excel[1])
                                        ? $excel[1]
                                        : array((string)$excel[1]);
                                $fn   = '$dataRow[\'' . implode('\'], $dataRow[\'', $fn) . '\']';
                                $fn   = 'return sprintf(\'' . $excel[0] . '\', '. $fn . ');';

                                $tdExcelFn = create_function('$dataRow', $fn);
                            }
                        } else if($tdFn) {
                            $tdExcelFn = $tdFn;
                        }

                        $tdExcelS = isset($c['excelStyle']) ? $c['excelStyle'] : null;

                        $formatExcelTableData []= array($tdExcelFn, $tdExcelS);
                    }
                }
            }

            $this->_formatTableHeader          = $formatTableHeader;
            $this->_formatTableData            = $formatTableData;

            $this->_formatExcelTableHeader     = $formatExcelTableHeader;
            $this->_formatExcelTableData       = $formatExcelTableData;

            $this->_resourceSortableFieldMap   = $sortableField;

            $this->_isResourceSetupTableFormat = true;
        }
        private function _setupResourcePaginator() {
            $this->setupResourceTableFormat()
                 ->setupResourceData();

            $paginationOptions = $this->_paginationOptions; // Performance tuning

            $paginator = Zend_Paginator::factory($this->_resourceData);
            $paginator->setCurrentPageNumber(
                            $this->_request->getParam(
                                $paginationOptions['currentPageNumber'],
                                $paginationOptions['defaultCurrentPageNumber']
                            )
                        );
            $paginator->setItemCountPerPage(
                            $this->_request->getParam(
                                $paginationOptions['itemCountPerPage'],
                                $paginationOptions['defaultItemCountPerPage']
                            )
                        );

            $this->_resourcePaginator = $paginator;
            $this->_isResourceSetupPaginator = true;
        }
        /**
         *  Data setupResource
         *      called by _setupResourcePaginator only
         *      in case data has not been setupResource yet
         */
        private function _setupResourceData() {
            $rq = $this->_request;

            // Sorting
            $sortOptions = $this->_sortOptions;
            $sortByIndex = $rq->getParam($sortOptions['sortIndexFieldCurrent'], null);

            if(isset($this->_resourceSortableFieldMap[$sortByIndex])) {
                $sortHow = ($rq->getParam($sortOptions['sortIndexFieldBefore'], 0) == $sortByIndex)
                           ? ' DESC'
                           : ' ASC';

                $this->_resourceData->reset(Zend_Db_Select::ORDER)
                                    ->order(array($this->_resourceSortableFieldMap[$sortByIndex] . $sortHow));
            }

            // Add rank
            $p = $this->_paginationOptions;
            $c = ($rq->getParam($p['currentPageNumber'], $p['defaultCurrentPageNumber']) - 1) *
                 $rq->getParam($p['itemCountPerPage'], $p['defaultItemCountPerPage']);
            $this->_resourceData->joinCross(new Zend_Db_Expr('(SELECT @rank := ' . $c . ')'), null)
                                ->columns(new Zend_Db_Expr('@rank := @rank + 1 AS _rank_'));

            $this->_isResourceSetupData = true;
        }

    // TOSTRING
        public function __toString() {
            try {
                $s = $this->getXhtml();
            } catch(Zend_Exception $e) {
                $s = 'Error at line ' . $e->getLine() . ' : ' . $e->getMessage() ;
            }
            return $s;
        }
    // Export
        // Shortcut
            public function exportXls($template=null , $templateKeyword=null, $phpExcelSheet=null){
                return $this->export('xls' , $template, $templateKeyword, $phpExcelSheet);
            }
            public function exportXlsx($template=null, $templateKeyword=null, $phpExcelSheet=null){
                return $this->export('xlsx', $template, $templateKeyword, $phpExcelSheet);
            }
            public function exportPdf($template=null , $templateKeyword=null, $phpExcelSheet=null){
                return $this->export('pdf' , $template, $templateKeyword, $phpExcelSheet);
            }
            public function exportCsv($template=null , $templateKeyword=null, $phpExcelSheet=null){
                return $this->export('csv' , $template, $templateKeyword, $phpExcelSheet);
            }
            public function exportHtml($template=null, $templateKeyword=null, $phpExcelSheet=null){
                return $this->export('html', $template, $templateKeyword, $phpExcelSheet);
            }

        public function export($exportType, $template=null, $templateKeyword=null) {
            if(!$templateKeyword) {
                $templateKeyword = $this->getExportKeywordTable();
            }

            // Default pivot to insert to
            $tplPivotRow    = 1;
            $tplPivotColumn = 0;

            $tplSheet = null;

            // Load template (if used)
            if(null !== $template && file_exists($template)) {
                $template = PHPExcel_IOFactory::load($template);
                if($template) {
                    $tplSheet = $template->getActiveSheet();

                    // Search for keyword AND update $tplPivot-
                    Apps_Plugin_Excel::searchForKeyword(
                        $templateKeyword, $tplSheet,
                        $tplPivotRow, $tplPivotColumn
                    );
                }
            }

            $excel = new Apps_Plugin_Excel();
            if($tplSheet instanceof PHPExcel_Worksheet) {
            	$excel->removeSheetByIndex(0)->addExternalSheet($tplSheet);
            }
            $tplSheet = $excel->getActiveSheet();


            $exportType = strtolower($exportType);
            switch($exportType) {
                case 'xls' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, array(), false);
                    $excel->downloadXls();
                    break;
                case 'xls-all' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, array(), true);
                    $excel->downloadXls();
                case 'xls-all-raw' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, null, true);
                    $excel->downloadXls();
                    break;

                case 'xlsx':
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, array(), false);
                    $excel->downloadXlsx();
                    break;
                case 'xlsx-all' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, array(), true);
                    $excel->downloadXlsx();
                case 'xlsx-all-raw' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn, null, true);
                    $excel->downloadXlsx();
                    break;

                case 'pdf' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn);
                    $excel->downloadPdf();
                    break;
                case 'csv' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn);
                    $excel->downloadCsv();
                    break;
                case 'html' :
                    $this->excelize($tplSheet, $tplPivotRow, $tplPivotColumn);
                    $excel->downloadHtml();
                    break;
            }
        }

    // Excelize
        public function excelize($sheet , &$startRow=1, $startCol=0, $styles=array(), $noPagination=true, $insertRow=true, $insertColumn=false, $startRowReference=true) {
            $book = null;
            if($sheet instanceof PHPExcel) {
                $book  = $sheet;
                $sheet = $sheet->getActiveSheet();
            }
            if(!($sheet instanceof PHPExcel_Worksheet)) {
                throw new Zend_Exception('PHPExcel_Worksheet required, plz.');
            }
            if(null == $book) {
                $book = $sheet->getParent();
            }

            set_time_limit(600); // Prevent timeout

            PHPExcel_Cell::setValueBinder(new PHPExcel_Cell_AdvancedValueBinder());
            $row = $startRow;

            // Style
                $headerHeight = 30;
                $bodyHeight = 25;

                $defaultStyles = self::$defaultExcelStyles;

                $noRaw = true;
                if(null === $styles) {
                    $styles = array();
                    $noRaw = false;
                } else if(is_array($styles)) {
                    $styles = array_merge($defaultStyles, $styles);
                } else {
                    $styles = $defaultStyles;
                }

                $idxPivotStyle = count($book->getCellXfCollection()); // User-defined style start from here
                foreach($styles as $k => $s) {
                    $style = new PHPExcel_Style();
                    $style->applyFromArray($s);

                    $book->addCellXf($style); // Add style to workbook
                }
                $mapBodyStyles = array(
                    self::EXCEL_STYLE_BODY_NORMAL     => $idxPivotStyle + 1,
                    self::EXCEL_STYLE_BODY_CENTER     => $idxPivotStyle + 2,
                    self::EXCEL_STYLE_BODY_CURRENCY_0 => $idxPivotStyle + 3,
                    self::EXCEL_STYLE_BODY_CURRENCY_1 => $idxPivotStyle + 4,
                    self::EXCEL_STYLE_BODY_DATE       => $idxPivotStyle + 5,

                    self::EXCEL_STYLE_LAST_BODY_NORMAL     => $idxPivotStyle + 6,
                    self::EXCEL_STYLE_LAST_BODY_CENTER     => $idxPivotStyle + 7,
                    self::EXCEL_STYLE_LAST_BODY_CURRENCY_0 => $idxPivotStyle + 8,
                    self::EXCEL_STYLE_LAST_BODY_CURRENCY_1 => $idxPivotStyle + 9,
                    self::EXCEL_STYLE_LAST_BODY_DATE       => $idxPivotStyle + 10,
                );

            // Excelize
            $colMaxWidth = array(); // To set column width according to content

                // Prepare place
                $this->setupResourcePaginator();
                if($noPagination) { $this->disablePagination(); }
                $resourcePaginator = $this->_resourcePaginator;
                $sheet->insertNewRowBefore($row + 1, $resourcePaginator->getTotalItemCount());

                // Header
                $formatTableHeader = $this->getFormatExcelTableHeader();

                // Set row height
                if($noRaw) {
                    $rowDimension = $sheet->getRowDimension($row);
                    $rowDimension->setRowHeight($headerHeight);
                }

                $col      = $startCol;      // Rewind column
                $idxStyle = $idxPivotStyle; // Style index


                foreach($formatTableHeader as $thFormat) {
                    $v = $thFormat['title'];

                    $cell = $sheet->getCellByColumnAndRow($col, $row)
                                  ->setValue($v);           /* Set cell value */

                    if($noRaw) {
                        $colMaxWidth[$col] = strlen($v); // Init column width tracker
                        $cell->setXfIndex($idxStyle); /* Set cell style */
                    }

                    ++$col;
                }
                ++$row;

                // Body
                    $formatExcelTableData = $this->getFormatExcelTableData();

                    $col      = $startCol;          // rewind column
                    $idxStyle = $idxPivotStyle + 1; // Style index

                    // Local sheet position
                    $indexRow = 1;
                    $indexCol = 0;

                    $lastRow = $resourcePaginator->getCurrentItemCount();
                    foreach($resourcePaginator as $dataRow) {
                        foreach($formatExcelTableData as $tdFormat) {
                            $tdContent = $tdFormat[0];
                            // Parse cell value
                                $v = $tdFormat ? $tdContent($dataRow, $row, $col, $indexRow, $indexCol) : '';

                                if(strpos($v, '<') !== false) {
                                    $v = preg_replace('/<br\s*\/?>/', "\n", strip_tags($v, '<br>'));
                                }

                            $cell = $sheet->getCellByColumnAndRow($col, $row)
                                          ->setValue($v);           /* Set cell value */

                            if($noRaw) {
                                // Style
                                $style = $tdFormat[1] ? $tdFormat[1] : self::EXCEL_STYLE_BODY_NORMAL;
                                if(!isset($mapBodyStyles[$style])) {
                                    $style = self::EXCEL_STYLE_BODY_NORMAL;
                                }
                                if($indexRow == $lastRow) {
                                    $style = 'last-' . $style;
                                }

                                $cell->setXfIndex($mapBodyStyles[$style]);

                                // Column width
                                $lenV = strlen($v);
                                if($lenV > $colMaxWidth[$col]) {
                                    $colMaxWidth[$col] = $lenV;
                                }
                            }

                            ++$col;
                            ++$indexCol;
                        }
                        // Set row height
                        $sheet->getRowDimension($row)->setRowHeight($bodyHeight);

                        ++$row;
                        ++$indexRow;

                        $col = $startCol; // rewind column
                    }

                // Set column width
                if($noRaw) {
                    foreach($colMaxWidth as $c => $width) {
                        $width = ($width + 10) / 2;
                        $sheet->getColumnDimensionByColumn($c)->setWidth($width);
                    }
                    // Set preset width column
                    foreach($formatTableHeader as $c => $thFormat) {
                        if(isset($thFormat['width'])) {
                            $w = $thFormat['width'];
                            $sheet->getColumnDimensionByColumn($c)->setWidth($w);
                        }
                    }
                }

            // Set auto value-binder
            PHPExcel_Cell::setValueBinder(new PHPExcel_Cell_DefaultValueBinder()); // Trick,
                                                                                   // force PHPExcel to rebind
                                                                                   // advanced value binder
                                                                                   // next time excelize invoked
            // Rebind
            if($startRowReference) {
                $startRow = $row;
            }
            return $this;
        }
        public static function excelColumnIndexToString($col) {
            return chr($col+65);
        }

    // HELPER
        private function _parseHtmlAttribute($attr) {
        	$xhtml = '';
        	if(is_array($attr)) {
	            foreach ($attr as $k => $v) {
	            	$xhtml .= ' ' . $k . '="' . $v . '"';
	            }
        	} else if(is_string($attr)) {
        		$xhtml = $attr;
        	}
            return $xhtml;
        }

    // GLOBAL INSTANCES
        public static function getAllInstances() {
            return self::$_instances;
        }
        protected static function addInstance(self $instance) {
            self::$_instances[$instance->getName()]= $instance;
        }
        public static function dump($label=null, $echo=true) {
            $instances = self::getAllInstances();

            $d = array();
            foreach ($instances as $k => $i) {
                if($i instanceof self) {
                    $d[$k] = $i->getData() . '';
                }
            }
            return Zend_Debug::dump($d, $label, $echo, false);
        }
}